;;;This file is in the public domain.

(cl:defpackage :iorek-tests
  (:use :iorek :fiveam :fset)
  (:export #:run-all-tests))

(cl:in-package :iorek-tests)

(def-suite iorek)

(def-suite symbols :in iorek)
(in-suite symbols)

(cl:defun to-string (object)
  (cl:with-output-to-string (s)
    (print-object object s)))

(test test-intern-1-level
  (cl:let* ((*namespace* iorek::*root-symbol*)
            (sym (intern "lang")))
    (is (cl:equal (symbol-namespace sym) *namespace*))
    (is (cl:equal "lang" (to-string sym)))
    (cl:let ((*namespace* (make-symbol)))
      (is (cl:equal ":lang" (to-string sym))))))

(test test-intern-2-level
  (cl:let* ((*namespace* iorek::*root-symbol*)
            (sym1 (intern "lang"))
            (sym2 (intern "defun" sym1)))
    (is (cl:equal (symbol-namespace sym2) sym1))
    (is (cl:equal "lang:defun" (to-string sym2)))
    (cl:let ((*namespace* (make-symbol)))
      (is (cl:equal ":lang:defun" (to-string sym2))))
    (cl:let ((*namespace* sym1))
      (is (cl:equal "defun" (to-string sym2))))))

(def-suite reader :in iorek)
(in-suite reader)

(test test-read-symbol
  (cl:let* ((*namespace* iorek::*root-symbol*)
            (sym1 (intern "lang"))
            (sym2 (intern "defun" sym1)))
    (is (cl:eq sym1 (read-object (cl:make-string-input-stream "lang"))))
    (is (cl:eq sym1 (read-object (cl:make-string-input-stream "lang:"))))
    (is (cl:eq sym1 (read-object (cl:make-string-input-stream ":lang"))))
    (is (cl:eq sym1 (read-object (cl:make-string-input-stream ":lang:"))))
    (is (cl:eq sym2 (read-object (cl:make-string-input-stream ":lang:defun"))))
    (is (cl:eq sym2 (read-object (cl:make-string-input-stream ":lang:defun:"))))
    (cl:let ((*namespace* sym1))
      (is (cl:eq sym2 (read-object (cl:make-string-input-stream "defun")))))
    (cl:let ((*namespace* (make-symbol :name "")))
      (is (cl:null (cl:ignore-errors (read-object (cl:make-string-input-stream "lang:defun")))))
      (is (cl:eq sym2 (read-object (cl:make-string-input-stream ":lang:defun")))))))

(test test-read-number
  (is (cl:equal 123 (read-object (cl:make-string-input-stream "123"))))
  (is (cl:equal -123 (read-object (cl:make-string-input-stream "-123"))))
  (is (cl:equal 13/17 (read-object (cl:make-string-input-stream "13/17"))))
  (is (cl:equal -13/17 (read-object (cl:make-string-input-stream "-13/17"))))
  (is (cl:equal #C(0 13/17) (read-object (cl:make-string-input-stream "13/17i"))))
  (is (cl:equal 123.45 (read-object (cl:make-string-input-stream "123.45"))))
  (is (cl:equal 123.45 (read-object (cl:make-string-input-stream "1.2345E+2"))))
  (is (cl:equal #C(0 123.45) (read-object (cl:make-string-input-stream "1.2345E+2i"))))
  (is (cl:equal #C(-0.0 -123.45) (read-object (cl:make-string-input-stream "-1.2345E+2i"))))) ;;0.0 and -0.0 are allowed to differ in CL.

(test test-read-sequence
  (is (cl:equal
       "(a 1 b 2 (c 3) d 4)"
       (to-string (read-object (cl:make-string-input-stream "(a 1 b 2 (c 3) d 4)"))))))

(test test-read-empty-vector
  (is (cl:equalp
       #()
       (read-object (cl:make-string-input-stream "[]")))))

(test test-read-vector
  (is (cl:equal
       "[a 1 b 2 [c 3] d 4]"
       (to-string (read-object (cl:make-string-input-stream "[a 1 b 2 [c 3] d 4]"))))))

(test test-read-empty-map
  (is (fset:equal?
       (fset:map)
       (read-object (cl:make-string-input-stream "{}")))))

(test test-read-map
  (is (cl:equal
       "{a 1 b 2 c {3 4} d 5}"
       (to-string (read-object (cl:make-string-input-stream "{a 1 b 2 c {3 4} d 5}"))))))

(test test-read-string
  (is (cl:equal
       "(a 1 b 2 \" (c 3) d 4)"
       (read-object (cl:make-string-input-stream "\"(a 1 b 2 \\\" (c 3) d 4)\"")))))

(test test-read-template
  (is (fset:equal?
       (read-object (cl:make-string-input-stream "(template (1 2 (insert 3) (insert-many (list 4)) (insert-many (list 5 6)) 7))"))
       (read-object (cl:make-string-input-stream "`(1 2 ,3 ,@(list 4) ,@(list 5 6) 7)")))))

(def-suite printer :in iorek)
(in-suite printer)

(test test-print-number
  (is (cl:equal "123.45" (to-string 123.45)))
  (is (cl:equal "1i" (to-string #C(0 1))))
  (is (cl:equal "123.45i" (to-string #C(0 123.45))))
  (is (cl:equal "[1.0 + 23.45i]" (to-string #C(1 23.45)))))

(def-suite templating :in iorek)
(in-suite templating)

(test test-template-1
  (is (fset:equal?
       (fset:seq 1 2 3 4 5 6 7)
       (cl:eval (iorek::translate-form (read-object (cl:make-string-input-stream "`(1 2 ,3 ,@(list 4) ,@(list 5 6) 7)")))))))

(def-suite evaluation :in iorek)
(in-suite evaluation)

(test test-eval-1
  (is (fset:equal?
       -4
       (cl:eval (iorek::translate-form (read-object (cl:make-string-input-stream "[1 - 2 - 3]")))))))

(test test-eval-2
  (is (fset:equal?
       21/5
       (cl:eval (iorek::translate-form (read-object (cl:make-string-input-stream "[1 * 2 + 3 - 4 / 5]")))))))

(test test-eval-3
  (is (fset:equal?
       (fset:seq 1 2 3)
       (cl:eval (iorek::translate-form (read-object (cl:make-string-input-stream "[(list 7 2 3) | subseq 1 || append (list 1)]")))))))

(cl:defun run-all-tests ()
  (run! 'iorek))
